/**
 *  device.h
 *
 *  Copyright (C) 2008-2009 ZhangHu
 *  All rights reserved.
 *  E-MAIL: anmnmnly@gmail.com
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "device.h"


/* Device list that store all device nodes. */
static list_t device_list[DEVICE_NR];

/* All entrys of device are stored here. */
extern device_init_func dev_init_func[DEVICE_NR];



/**
 * device_add - Add a device to device list.
 * @Dev: Pointer to device node.
 * @Name: Device name.
 * @Mid: Main ID.
 * @Sid: Secondary ID.
 * @return: DEV_OK if successful, otherwise DEV_FALSE or DEV_NO_MEM.
 *
 * @notes:
 */
static word_t device_add(device_t *Dev, const char_t *Name,
                         char_t Mid, char_t Sid,
                         void (*release)(void)) {
    char_t *pname = NULL;
    list_t *plist = device_list[Dev->mid].next;
    device_t *pdev = NULL;


    if((Dev == NULL) || (Name == NULL) || (Mid >= DEVICE_NR)) {
        return DEV_FALSE;
    }

    /* Ensure that the Sid is not registered. */
    do {
        pdev = mac_find_entry(plist, device_t, link);
        if((pdev->sid == Sid) || (lib_strcmp(pdev->name, Name) == 0)) {
            return DEV_FALSE;
        }
    }while(plist != device_list[Dev->mid].next);


    if((pname = (char_t*)kmalloc(sizeof(Name) + 1)) == NULL) {
        return DEV_NO_MEM;
    }

    list_node_init(&Dev->link);
    lib_strcpy(pname, Name);
    Dev->name = pname;
    Dev->mid = Mid;
    Dev->sid = Sid;
    Dev->used_cnt = 0;
    Dev->delete_flag = FALSE;
    Dev->release = release;
    mac_disable_irq();
    add_node_list_rear(&device_list[Dev->mid], &Dev->link);
    mac_enable_irq();
    return DEV_OK;
}



/**
 * device_delete - Delete a device from device list.
 * @Mid: Main ID.
 * @Sid: Secondary ID.
 *
 * @notes:
 */
static void device_delete(uword_t chdevMID, uword_t chdevSID) {
    list_t *plist;
    device_t *dev;

    if(chdevMID >= DEVICE_NR) {
        return;
    }

    mac_disable_irq();
    plist = device_list[chdevMID].next;
    if(plist == &device_list[chdevMID]) {
        mac_enable_irq();
        return;
    }

    do {
        dev = mac_find_entry(plist, device_t, link);
        if(dev->sid == chdevSID) {
            if((dev->delete_flag == TRUE) && (dev->used_cnt <= 0)) {
                del_node_list(&device_list[chdevMID], plist);
                mac_enable_irq();
                if(dev->release != NULL) {
                    dev->release();
                }
                kfree(dev->name);
                kfree(dev);
                return;
            } else {
                dev->delete_flag = TRUE;
                break;
            }
        }
        plist = plist->next;
    } while(plist != device_list[chdevMID].next);

    mac_enable_irq();
    return;
}



/**
 * char_device_register - Register a char device.
 * @Dev: Pointer to device node.
 * @Name: Device name.
 * @Mid: Main ID.
 * @Sid: Secondary ID.
 * @return: DEV_OK if successful, otherwise DEV_FALSE or DEV_NO_MEM.
 *
 * @notes:
 */
word_t char_device_register(device_t *dev, const char_t *Name,
                            char_t Mid, char_t Sid,
                            void (*release)(void)) {
    if((dev == NULL) || (Name == NULL) || (Mid >= DEVICE_NR)) {
        return DEV_FALSE;
    }

    dev->dev_type = CHDEV;
    return device_add(dev, Name, Mid, Sid, release);
}



/**
 * char_device_unregister - Unregister a char device.
 * @Mid: Main ID.
 * @Sid: Secondary ID.
 *
 * @notes:
 */
void char_device_unregister(uword_t chdevMID, uword_t chdevSID) {
    device_delete(chdevMID, chdevSID);
}



/**
 * device_init - Initialize device list and all devices.
 * @return:
 *
 * @notes:
 */
void device_init(void) {
    word_t i;

    for(i=0; i<DEVICE_NR; i++) {
        device_list[i].next = &device_list[i];
        device_list[i].prev = &device_list[i];
    }

	i = 0;
	while((dev_init_func[i] != NULL) && (i < DEVICE_NR)) {
		dev_init_func[i]();
		i++;
	}
}



/**
 * device_open - Open a device.
 * @Mid: Main ID.
 * @Sid: Secondary ID.
 * @return: Device node.
 *
 * @notes:
 */
device_t *device_open(const char_t *name) {
    word_t i = 0;
    list_t *list = NULL;
    device_t *dev = NULL;

    mac_disable_irq();
    for(i=0; i<DEVICE_NR; i++) {
        list = device_list[i].next;
        do {
            dev = mac_find_entry(list, device_t, link);
            if(lib_strcmp(dev->name, name) == 0) {
                if(dev->delete_flag == TRUE) {
                    mac_enable_irq();
                    return NULL;
                }
                dev->used_cnt++;
                mac_enable_irq();
                return dev;
            } else {
                list = list->next;
            }
        }while(list != device_list[i].next);
    }

    mac_enable_irq();
    return NULL;
}



/**
 * device_close - Close a device.
 * @Dev: Pointer to device node.
 * @return:
 *
 * @notes: descread used count, and delete device if necessary.
 */
void device_close(device_t *dev) {
    if(dev == NULL) {
        return;
    }

    mac_disable_irq();
    dev->used_cnt--;

    if((dev->delete_flag == TRUE) && (dev->used_cnt <= 0)) {
        del_node_list(&device_list[dev->mid], &dev->link);
        mac_enable_irq();
        if(dev->release != NULL) {
            dev->release();
        }
        kfree(dev->name);
        kfree(dev);
    }

    mac_enable_irq();
}


